package net.guerlab.smart.wx.web.controller.user.wx.mp;

import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;
import lombok.extern.slf4j.Slf4j;
import me.chanjar.weixin.mp.api.WxMpUserService;
import me.chanjar.weixin.mp.bean.result.WxMpUser;
import me.chanjar.weixin.mp.bean.result.WxMpUserList;
import net.guerlab.smart.platform.user.api.OperationLogApi;
import net.guerlab.smart.platform.user.auth.UserContextHandler;
import net.guerlab.smart.wx.core.exception.WxAppInvalidException;
import net.guerlab.smart.wx.core.exception.WxMpUserSynchronizingException;
import net.guerlab.smart.wx.service.entity.WxApp;
import net.guerlab.smart.wx.service.entity.WxUser;
import net.guerlab.smart.wx.service.service.WxAppService;
import net.guerlab.smart.wx.service.service.WxMpManagerService;
import net.guerlab.smart.wx.service.service.WxUserService;
import net.guerlab.smart.wx.web.domain.UserSyncStatus;
import net.guerlab.smart.wx.web.enums.UserSyncProcess;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * 微信-公众号-用户管理
 *
 * @author guer
 */
@Slf4j
@Api(tags = "微信-公众号-用户管理")
@RestController("/user/wx/mp/user")
@RequestMapping("/user/wx/mp/user")
public class UserController {

    private static final int SINGLE_LIMIT = 100;

    private static final int AVAILABLE_PROCESSORS = Runtime.getRuntime().availableProcessors();

    private final ThreadPoolExecutor executor = new ThreadPoolExecutor(AVAILABLE_PROCESSORS, AVAILABLE_PROCESSORS * 2,
            1L, TimeUnit.MINUTES, new LinkedBlockingQueue<>(), r -> {
        Thread thread = new Thread(r);
        thread.setName("async_wx_mp_user");
        return thread;
    });

    private final Map<String, AtomicBoolean> lockMap = new ConcurrentHashMap<>(8);

    private final Map<String, UserSyncStatus> statusMap = new ConcurrentHashMap<>(8);

    private WxMpManagerService mpManagerService;

    private WxUserService wxUserService;

    private WxAppService wxAppService;

    private OperationLogApi operationLogApi;

    @ApiOperation("获取同步用户信息进度")
    @GetMapping("/{appId}/userSync")
    public UserSyncStatus getUserSyncStatus(@ApiParam(value = "应用ID", required = true) @PathVariable String appId) {
        UserSyncStatus status = statusMap.get(appId);
        if (status == null) {
            status = new UserSyncStatus();
            status.setProcess(UserSyncProcess.FREE);
        }

        return status;
    }

    @ApiOperation("同步用户信息")
    @PostMapping("/{appId}/userSync")
    public void userSync(@ApiParam(value = "应用ID", required = true) @PathVariable String appId) {
        final AtomicBoolean flag = lockMap.computeIfAbsent(appId, s -> new AtomicBoolean());

        if (!flag.compareAndSet(false, true)) {
            throw new WxMpUserSynchronizingException();
        }

        final UserSyncStatus status = statusMap.computeIfAbsent(appId, s -> new UserSyncStatus());
        status.setProcess(UserSyncProcess.PENDING_START);

        WxApp wxApp = wxAppService.selectByIdOptional(appId).orElseThrow(WxAppInvalidException::new);
        executor.execute(() -> {
            try {
                userSyncThread(appId, wxApp.getAppName(), status);
            } catch (Exception e) {
                log.debug(e.getLocalizedMessage(), e);
                status.setProcess(UserSyncProcess.EXCEPTION);
                status.setErrorMessage(e.getLocalizedMessage());
            } finally {
                flag.set(false);
            }
        });
        operationLogApi.add("同步公众号用户", UserContextHandler.getUserId(), appId);
    }

    private void userSyncThread(String appId, String appName, UserSyncStatus status) throws Exception {
        WxMpUserService userService = mpManagerService.getService(appId).getUserService();
        status.setProcess(UserSyncProcess.GETTING_OPENID_LIST);
        List<String> openIds = getOpenIds(userService);
        if (!openIds.isEmpty()) {
            userSync(appId, appName, openIds, userService, status);
        }
    }

    private List<String> getOpenIds(WxMpUserService userService) throws Exception {
        boolean hasMore = true;
        String nextOpenid = null;
        List<String> openIds = new ArrayList<>();

        while (hasMore) {
            WxMpUserList wxMpUserList = userService.userList(nextOpenid);
            nextOpenid = wxMpUserList.getNextOpenid();
            hasMore = StringUtils.isNotBlank(nextOpenid);
            List<String> tempOpenIds = wxMpUserList.getOpenids();

            if (tempOpenIds.isEmpty()) {
                break;
            }

            openIds.addAll(tempOpenIds);
        }

        return openIds;
    }

    private void userSync(String appId, String appName, List<String> openIds, WxMpUserService userService,
            UserSyncStatus status) throws Exception {
        int size = openIds.size();
        status.setCount(size);
        status.setProcess(UserSyncProcess.GETTING_USER_DETAIL);

        AtomicInteger complete = new AtomicInteger();

        for (int i = 0; i < size; i += SINGLE_LIMIT) {
            int end = Math.min(i + SINGLE_LIMIT, size) - 1;
            List<String> queryOpenIds = openIds.subList(i, end);
            List<WxMpUser> wxMpUsers = userService.userInfoList(queryOpenIds);

            for (WxMpUser wxMpUser : wxMpUsers) {
                userSyncHandler(appId, appName, wxMpUser);
                status.setComplete(complete.addAndGet(1));
            }
        }
        status.setProcess(UserSyncProcess.FREE);
    }

    private void userSyncHandler(String appId, String appName, WxMpUser user) {
        WxUser wxUser = wxUserService.findUser(appId, user.getOpenId());

        if (wxUser == null) {
            wxUser = new WxUser();
            wxUser.setOpenId(user.getOpenId());
            wxUser.setAppId(appId);
            wxUser.setUnionId(user.getUnionId());
            wxUser.setAppName(appName);
            wxUser.setUnionId(user.getUnionId());
            wxUser.setAvatarUrl(user.getHeadImgUrl());
            wxUser.setNickName(user.getNickname());

            wxUserService.insertSelective(wxUser);
        } else {
            WxUser update = new WxUser();
            update.setOpenId(wxUser.getOpenId());
            update.setAppId(appId);
            update.setUnionId(user.getUnionId());
            update.setAvatarUrl(user.getHeadImgUrl());
            update.setNickName(user.getNickname());
            update.setVersion(wxUser.getVersion());

            wxUserService.updateSelectiveById(update);
        }
    }

    @Autowired
    public void setMpManagerService(WxMpManagerService mpManagerService) {
        this.mpManagerService = mpManagerService;
    }

    @Autowired
    public void setWxUserService(WxUserService wxUserService) {
        this.wxUserService = wxUserService;
    }

    @Autowired
    public void setWxAppService(WxAppService wxAppService) {
        this.wxAppService = wxAppService;
    }

    @SuppressWarnings("SpringJavaInjectionPointsAutowiringInspection")
    @Autowired
    public void setOperationLogApi(OperationLogApi operationLogApi) {
        this.operationLogApi = operationLogApi;
    }
}
